package com.ideal.frame.node.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ideal.frame.common.dto.ResponseModel;
import com.ideal.frame.common.enumerate.ResponseCode;
import com.ideal.frame.config.RootProperties;
import com.ideal.frame.node.entity.NodeSyncEvent;
import com.ideal.frame.node.entity.NodeTb;
import com.ideal.frame.node.entity.RootNodeTb;
import com.ideal.frame.node.form.search.NodeSyncParams;
import com.ideal.frame.node.form.search.RootNodeSyncParams;
import com.ideal.frame.prefix.entity.PrefixNetxTb;
import com.ideal.frame.prefix.form.search.PrefixSyncParams;
import com.ideal.frame.prefix.service.PrefixNextTbService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: zhangdelei
 * @date: 2021/9/14-3:10 下午
 * @Description:
 **/
@Slf4j
@Service
public class NodeSyncService {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private NodeSyncEventService nodeSyncEventService;
    @Autowired
    private RootNodeService rootNodeService;
    @Autowired
    private PrefixNextTbService prefixNextTbService;
    @Autowired
    private NodeService nodeService;
    @Autowired
    private RootProperties rootProperties;
    @Value("${node.level}")
    private Integer nodeLevel;
    @Value("${node.prefix}")
    private String nodePrefix;
    // region 同步根节点

    /**
     * 开始同步根节点(只有原始根节点需要调用此方法)
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncRootData() {
        // 获取所有需要同步的节点列表
        List<RootNodeTb> allRoots = rootNodeService.allData();
        if (allRoots != null && allRoots.size() > 0) {
            allRoots.forEach(t -> syncRootData(t, rootProperties.getRetry()));
        }
    }

    /**
     * 同步根节点数据
     *
     * @param retryCount
     */
    private void syncRootData(RootNodeTb root, int retryCount) {
        if (rootProperties.getRetry() > 0) {
            retryCount--;
        }
        int finalRetryCount = retryCount;
        ThreadUtil.execAsync(() -> {
            // 获取上一次同步的时间点
            NodeSyncEvent event = nodeSyncEventService.lastSync(0, root.getPrefixCode());
            // 获取需要同步的数据
            List<RootNodeTb> rootNodeTbs = rootNodeService.findBySyncTime(RootNodeSyncParams.builder().syncTime(event == null ? null : event.getSyncTime()).build());
            if (rootNodeTbs != null && rootNodeTbs.size() > 0) {
                String url = rootProperties.getSyncRootData().replace("{ip}", root.getNodeip()).replace("{port}", root.getNodeport().toString());
                ResponseModel<String> response = post(rootNodeTbs, url, null);
                if (ResponseCode.SUCCESS.getCode().equals(response.getCode())) {
                    log.info("同步成功");
                    // 保存同步时间节点
                    nodeSyncEventService.saveSyncEvent(0, rootNodeTbs.get(rootNodeTbs.size() - 1).getUpdatedate(), root.getPrefixCode());
                } else {
                    // 开始等待延时
                    if (rootProperties.getRetry() >= 0) {
                        try {
                            TimeUnit.SECONDS.sleep(rootProperties.getRetryTime());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 重试机制（-1不重试，0不间断重试）
                    if (rootProperties.getRetry() == 0) {
                        syncRootData(root, 0);
                    } else if (rootProperties.getRetry() > 0) {
                        if (finalRetryCount > 0) {
                            syncRootData(root, finalRetryCount);
                        }
                    }
                }
            }
        });
    }

    public static void main(String[] args) {
        String aa = String.format("%s:%s", "127.0.0.1", 18089);
        System.out.println(aa);
    }

    // endregion

    // region 同步前缀逻辑

    /**
     * 同步前缀
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncPrefixNode() {
        // 获取所有需要同步的节点列表
        List<RootNodeTb> allRoots = rootNodeService.allData();
        if (nodeLevel == -1) {
            addRootNode(allRoots);
        }
        log.info("开始同步前缀信息：" + JSONUtil.toJsonStr(allRoots));
        if (allRoots != null && allRoots.size() > 0) {
            allRoots.forEach(t -> syncPrefixData(t, rootProperties.getRetry()));
        }
    }

    /**
     * 同步根节点数据
     *
     * @param retryCount
     */
    private void syncPrefixData(RootNodeTb root, int retryCount) {
        log.info("同步前缀：" + JSONUtil.toJsonStr(root));
        if (rootProperties.getRetry() > 0) {
            retryCount--;
        }
        int finalRetryCount = retryCount;
        ThreadUtil.execAsync(() -> {
            // 获取上一次同步的时间点
            NodeSyncEvent event = nodeSyncEventService.lastSync(1, root.getPrefixCode());
            // 获取所有待同步的前缀列表
            List<PrefixNetxTb> syncPrefixList = prefixNextTbService.findBySyncTime(PrefixSyncParams.builder().prefix(nodePrefix).syncTime(event == null ? null : event.getSyncTime()).build());
            if (syncPrefixList != null && syncPrefixList.size() > 0) {
                String syncUrl = rootProperties.getSyncPrefixData().replace("{ip}", root.getNodeip()).replace("{port}", root.getNodeport().toString());
                ResponseModel<String> responseSync = post(syncPrefixList, syncUrl, null);
                if (ResponseCode.SUCCESS.getCode().equals(responseSync.getCode())) {
                    log.info("同步成功:" + root.getPrefixCode() + ":" + root.getNodeip() + ":" + DateUtil.date().toDateStr());
                    // 保存同步时间节点
                    nodeSyncEventService.saveSyncEvent(1, syncPrefixList.get(syncPrefixList.size() - 1).getUpdatedate(), root.getPrefixCode());
                } else {
                    // 开始等待延时
                    if (rootProperties.getRetry() >= 0) {
                        try {
                            TimeUnit.SECONDS.sleep(rootProperties.getRetryTime());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 重试机制（-1不重试，0不间断重试）
                    if (rootProperties.getRetry() == 0) {
                        syncPrefixData(root, 0);
                    } else if (rootProperties.getRetry() > 0) {
                        if (finalRetryCount > 0) {
                            syncPrefixData(root, finalRetryCount);
                        }
                    }
                }
            }
        });
    }

    /**
     * 初始化同步前缀数据
     */
    public void initSyncPrefixData() {
        // 获取上一次同步的时间点
        NodeSyncEvent event = nodeSyncEventService.lastSync(1, nodePrefix);
        // 获取所有待同步的节点列表
        PrefixSyncParams params = PrefixSyncParams.builder().syncTime(event == null ? null : event.getSyncTime()).prefix(nodePrefix).build();
        String prefixSyncUrl = rootProperties.getFindSyncPrefix().replace("{ip}", rootProperties.getRootIp()).replace("{port}", String.valueOf(rootProperties.getRootPort()));
        ResponseModel<List<PrefixNetxTb>> resPrefix = post(params, prefixSyncUrl, null);
        log.info("获取到的待同步前缀信息：============================" + JSONUtil.toJsonStr(resPrefix.getData()));
        List<PrefixNetxTb> prefixNetxTbs = JSONUtil.toList(JSONUtil.parseArray(JSONUtil.toJsonStr(resPrefix.getData())), PrefixNetxTb.class);
        if (prefixNetxTbs == null) {
            prefixNetxTbs = Collections.emptyList();
        }
        if (prefixNetxTbs != null && prefixNetxTbs.size() > 0) {
            // 开始同步
            prefixNextTbService.syncPrefixData(prefixNetxTbs);
            // 保存同步时间节点
            nodeSyncEventService.saveSyncEvent(1, prefixNetxTbs.get(prefixNetxTbs.size() - 1).getUpdatedate(), nodePrefix);
        }
    }

    // endregion

    // region 同步节点逻辑

    /**
     * 同步前缀
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncNode() {
        // 获取所有需要同步的节点列表
        List<RootNodeTb> allRoots = rootNodeService.allData();
        log.info("开始同步节点数据：=================================共有根节点:" + allRoots.size());
        if (nodeLevel == -1) {
            addRootNode(allRoots);
        }
        if (allRoots != null && allRoots.size() > 0) {
            allRoots.forEach(t -> syncNodeData(t, rootProperties.getRetry()));
        }
    }

    /**
     * 同步根节点数据
     *
     * @param root
     */
    private void syncNodeData(RootNodeTb root, int retryCount) {
        if (rootProperties.getRetry() > 0) {
            retryCount--;
        }
        int finalRetryCount = retryCount;
        ThreadUtil.execAsync(() -> {
            // 获取上一次同步的时间点
            NodeSyncEvent event = nodeSyncEventService.lastSync(2, root.getPrefixCode());
            // 获取所有待同步的节点列表
            List<NodeTb> roots = nodeService.findSyncNodeData(NodeSyncParams.builder().syncTime(event == null ? null : event.getSyncTime()).prefix(nodePrefix).build());
            if (roots == null) {
                roots = Collections.emptyList();
            }
            if (roots != null && roots.size() > 0) {
                String urlSync = rootProperties.getSyncNodeData().replace("{ip}", root.getNodeip()).replace("{port}", root.getNodeport().toString());
                log.info("同步请求地址：" + urlSync);
                ResponseModel<String> responseSync = post(roots, urlSync, null);
                if (ResponseCode.SUCCESS.getCode().equals(responseSync.getCode())) {
                    log.info("同步成功:" + root.getPrefixCode() + ":" + root.getNodeip() + ":" + DateUtil.date().toDateStr());
                    // 保存同步时间节点
                    nodeSyncEventService.saveSyncEvent(2, roots.get(roots.size() - 1).getUpdatedate(), root.getPrefixCode());
                } else {
                    // 开始等待延时
                    if (rootProperties.getRetry() >= 0) {
                        try {
                            TimeUnit.SECONDS.sleep(rootProperties.getRetryTime());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 重试机制（-1不重试，0不间断重试）
                    if (rootProperties.getRetry() == 0) {
                        syncNodeData(root, 0);
                    } else if (rootProperties.getRetry() > 0) {
                        if (finalRetryCount > 0) {
                            syncNodeData(root, finalRetryCount);
                        }
                    }
                }
            }
        });
    }

    /**
     * 初始化同步节点数据
     */
    public void initSyncNodeData() {
        // 获取上一次同步的时间点
        NodeSyncEvent event = nodeSyncEventService.lastSync(2, nodePrefix);
        // 获取所有待同步的节点列表
        NodeSyncParams params = NodeSyncParams.builder().syncTime(event == null ? null : event.getSyncTime()).prefix(nodePrefix).build();
        String nodeSyncUrl = rootProperties.getFindSyncNode().replace("{ip}", rootProperties.getRootIp()).replace("{port}", String.valueOf(rootProperties.getRootPort()));
        ResponseModel<List<NodeTb>> resNode = post(params, nodeSyncUrl, null);
        log.info("获取到的待同步节点信息：============================" + JSONUtil.toJsonStr(resNode.getData()));
        List<NodeTb> nodes = JSONUtil.toList(JSONUtil.parseArray(JSONUtil.toJsonStr(resNode.getData())), NodeTb.class);
        if (nodes == null) {
            nodes = Collections.emptyList();
        }
        if (nodes != null && nodes.size() > 0) {
            // 开始同步
            nodeService.syncNodeData(nodes);
            // 保存同步时间节点
            nodeSyncEventService.saveSyncEvent(2, nodes.get(nodes.size() - 1).getUpdatedate(), nodePrefix);
        }
    }
    // endregion

    private void addRootNode(List<RootNodeTb> allRoots) {
        RootNodeTb root = new RootNodeTb();
        root.setNodeip(rootProperties.getRootIp());
        root.setNodeport(rootProperties.getRootPort());
        root.setPrefixCode(rootProperties.getRootPrefix());
        allRoots.add(root);
    }

    /**
     * 通用post请求
     *
     * @param params
     * @param url
     * @param token
     * @param <T>
     * @return
     */
    public <T> ResponseModel post(T params, String url, String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        if (StringUtils.hasText(token)) {
            headers.set("Authorization", token);
        }
        HttpEntity<T> request = new HttpEntity<>(params, headers);
        log.info("请求地址：" + url);
        log.info("请求参数:" + JSONUtil.toJsonStr(params));
        if (StringUtils.isEmpty(url)) {
            return null;
        }
        log.info("接口入参：" + JSONUtil.toJsonStr(params));
        ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
        log.info("请求返回结果：" + response.getBody());
        if (response != null) {
            String data = response.getBody();
            ObjectMapper om = new ObjectMapper();
            try {
                return om.readValue(data, ResponseModel.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        } else {
            return ResponseModel.error(ResponseCode.INVALID_PARAMETER);
        }
    }
}
